#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
功能：解析由 receiver.py 生成的 receive_packet 日志文件，判定：
  1) 哪些帧的 data 包发生了丢失（D < N）；
  2) 哪些帧可由 FEC 恢复（D + F >= N）；
  3) 哪些帧可由 XOR 恢复（D + F == N - 1，且存在 XOR 组中仅此一帧不完整）。

用法：
  python analyze_receive_packets.py --path /path/to/receive_packet_xxx.txt

重要说明：
  - N (frame_pkt_num) 仅来源于 Data 行。若某帧从未出现 Data 行，将视为 N 未知，无法做严格判定，脚本会标注为 unknown_N。
  - XOR 可恢复的判断采用“组内仅一帧不完整”的条件，匹配接收端逻辑的 missing_cnt<=1 可解。
"""

import argparse
from pathlib import Path
from typing import Dict, Set, List, Tuple, Optional

# =========================
# 数据结构与变量释义
# =========================
# frames_required_data_cnt: Dict[int, int]
#   - 键：frame_id
#   - 值：该帧所需的 data 分片总数 N（来自 data 行的 frame_pkt_num）
#
# frames_data_pkt_ids: Dict[int, Set[int]]
#   - 键：frame_id
#   - 值：该帧已收到的 data 分片编号集合（用于计算 D，不重复计数）
#
# frames_fec_cnt: Dict[int, int]
#   - 键：frame_id
#   - 值：该帧收到的 FEC 包数量（用于计算 F）
#
# xor_groups: List[Set[int]]
#   - 每个元素是一个 XOR 组涉及的帧集合（由 "xor ... frames=fid1,fid2,..." 提取）
#
# unknown_N_frames: Set[int]
#   - 仅在某帧从未出现 data 行、但出现 fec/xor 里被引用时，这类帧会进入 unknown_N（N未知）。
#
# 注：所有新变量在定义处都附带注释，所有新代码块前都有“=== 功能说明 ===”注释块。

def parse_log_file(path: Path):
    """
    逐行解析 receive_packet 日志文件，填充上述数据结构。
    """
    frames_required_data_cnt: Dict[int, int] = {}
    frames_data_pkt_ids: Dict[int, Set[int]] = {}
    frames_fec_cnt: Dict[int, int] = {}
    xor_groups: List[Set[int]] = []
    unknown_N_frames: Set[int] = set()

    def _to_int(s: str) -> Optional[int]:
        try:
            return int(s)
        except Exception:
            return None

    with path.open("r", encoding="utf-8") as f:
        for line_no, line in enumerate(f, 1):
            line = line.strip()
            if not line:
                continue
            parts = line.split("\t")
            # 基本健壮性：至少要有 3 列（packet_id, frame_id/NA, type）
            if len(parts) < 3:
                print(f"[WARN] 第{line_no}行格式异常：{line}")
                continue

            typ = parts[2].strip().lower()

            # === data 行 ===
            if typ == "data":
                # 预期 7 列: pid, fid, data, frame_pkt_num, frame_len, pkt_id_in_frame, packet_size
                if len(parts) < 7:
                    print(f"[WARN] 第{line_no}行 data 列数不足：{line}")
                    continue
                fid = _to_int(parts[1])
                N   = _to_int(parts[3])  # frame_pkt_num
                pkt = _to_int(parts[5])  # pkt_id_in_frame
                if fid is None or N is None or pkt is None:
                    print(f"[WARN] 第{line_no}行 data 解析失败：{line}")
                    continue

                # 记录 N（若多次出现，以最大值为准更稳妥）
                oldN = frames_required_data_cnt.get(fid, 0)
                if N > oldN:
                    frames_required_data_cnt[fid] = N

                # 记录 data 分片编号
                frames_data_pkt_ids.setdefault(fid, set()).add(pkt)

            # === fec 行 ===
            elif typ == "fec":
                # 预期 7 列: pid, fid, fec, 0, 0, 0, packet_size
                if len(parts) < 2:
                    print(f"[WARN] 第{line_no}行 fec 列数不足：{line}")
                    continue
                fid = _to_int(parts[1])
                if fid is None:
                    # 保险：fec 行理论上都有 frame_id
                    print(f"[WARN] 第{line_no}行 fec 的 frame_id 异常：{line}")
                    continue
                frames_fec_cnt[fid] = frames_fec_cnt.get(fid, 0) + 1
                # 若这个 fid 还没出现过 data 行，则 N 未知
                if fid not in frames_required_data_cnt:
                    unknown_N_frames.add(fid)

            # === xor 行 ===
            elif typ == "xor":
                # 形式：pid, NA, xor, frames=fid1,fid2,...
                # 我们只关心 frames=... 这一列
                # 允许该列在 parts[3] 或更后面（更健壮起见做一遍查找）
                frames_col = None
                for col in parts[3:]:
                    if col.startswith("frames="):
                        frames_col = col
                        break
                if frames_col is None:
                    print(f"[WARN] 第{line_no}行 xor 未找到 frames= 列：{line}")
                    continue

                # 解析帧集合
                try:
                    _, fid_list_str = frames_col.split("=", 1)
                    fid_list = [int(x) for x in fid_list_str.split(",") if x.strip() != ""]
                except Exception:
                    print(f"[WARN] 第{line_no}行 xor 解析 frames= 失败：{line}")
                    continue

                if not fid_list:
                    continue
                xor_groups.append(set(fid_list))
                # XOR 提到的 fid 里，有些可能从未出现 data 行
                for fid in fid_list:
                    if fid not in frames_required_data_cnt:
                        unknown_N_frames.add(fid)

            else:
                print(f"[WARN] 第{line_no}行未知类型 typ={typ}：{line}")
                continue

    return frames_required_data_cnt, frames_data_pkt_ids, frames_fec_cnt, xor_groups, unknown_N_frames


def decide_sets(frames_required_data_cnt: Dict[int, int],
                frames_data_pkt_ids: Dict[int, Set[int]],
                frames_fec_cnt: Dict[int, int],
                xor_groups: List[Set[int]]):
    """
    基于统计结果，输出三类集合：
      - lost_data_frames：data 丢失（D < N）
      - fec_recoverable_frames：可由 FEC 直接补齐（D + F >= N）
      - xor_recoverable_frames：可由 XOR 补齐（D + F == N - 1 且存在 XOR 组中仅此一帧不完整）
    返回：(lost_data_frames, fec_recoverable_frames, xor_recoverable_frames)
    """
    # === 功能说明 ===
    # 1) 预先计算每个帧的 D、F、N
    # 2) lost_data: D < N
    # 3) fec_recoverable: D + F >= N
    # 4) xor_recoverable: D + F == N - 1 且存在 XOR 组仅有此一帧不完整

    D_map: Dict[int, int] = {fid: len(pkts) for fid, pkts in frames_data_pkt_ids.items()}
    F_map: Dict[int, int] = frames_fec_cnt.copy()
    N_map: Dict[int, int] = frames_required_data_cnt.copy()

    # 为所有在 N_map 中出现的帧填齐默认 D、F 值
    for fid in N_map.keys():
        if fid not in D_map:
            D_map[fid] = 0
        if fid not in F_map:
            F_map[fid] = 0

    # 先计算“完整性”的布尔图，供 XOR 判定引用
    def is_complete(fid: int) -> bool:
        D = D_map.get(fid, 0)
        F = F_map.get(fid, 0)
        N = N_map.get(fid, None)
        return (N is not None) and (D + F >= N)

    lost_data_frames: Set[int] = set()
    fec_recoverable_frames: Set[int] = set()
    xor_recoverable_frames: Set[int] = set()

    # 逐帧判断 data 丢失 / FEC 可恢复
    for fid, N in N_map.items():
        D = D_map.get(fid, 0)
        F = F_map.get(fid, 0)
        if D < N:
            lost_data_frames.add(fid)
        if D < N and D + F >= N:
            fec_recoverable_frames.add(fid)

    # XOR 可恢复：D + F == N - 1 且存在 XOR 组仅此一帧不完整
    # 做一个从 fid -> 出现在哪些 XOR 组的倒排，减少遍历
    fid_to_xorgroups: Dict[int, List[Set[int]]] = {}
    for g in xor_groups:
        for fid in g:
            fid_to_xorgroups.setdefault(fid, []).append(g)

    for fid, N in N_map.items():
        D = D_map.get(fid, 0)
        F = F_map.get(fid, 0)
        if D + F != N - 1:
            continue  # 不满足“只差 1”
        # 该帧是否存在某个 XOR 组，其中其他帧都已完整？
        groups = fid_to_xorgroups.get(fid, [])
        for g in groups:
            others = [x for x in g if x != fid]
            # 其他帧如果“全部完整”，则该 XOR 组 missing_cnt==1，可解
            if all(is_complete(o) for o in others):
                xor_recoverable_frames.add(fid)
                break

    return lost_data_frames, fec_recoverable_frames, xor_recoverable_frames


def main():
    # === 功能说明 ===
    # CLI 入口：读取 --path，解析日志，输出三类结果。
    parser = argparse.ArgumentParser(description="Analyze receive_packet log for data-loss / FEC-recoverable / XOR-recoverable frames.")
    parser.add_argument("--path", type=str, required=True, help="receive_packet 日志文件路径（例如 /data/.../receive_packet_2025xxxx.txt）")
    args = parser.parse_args()

    path = Path(args.path)
    if not path.exists():
        print(f"[ERROR] 文件不存在：{path}")
        return

    (N_map, data_ids_map, fec_cnt_map, xor_groups, unknown_N) = parse_log_file(path)

    # 计算三类集合（对 N 已知的帧）
    lost_data, fec_ok, xor_ok = decide_sets(N_map, data_ids_map, fec_cnt_map, xor_groups)

    # === 结果打印（只打印到 stdout，不落盘） ===
    # 1) 基本统计
    total_frames_knownN = len(N_map)
    print("==== 基本统计 ====")
    print(f"总帧数（N已知）      : {total_frames_knownN}")
    print(f"出现过但N未知的帧数 : {len(unknown_N)}")
    print(f"XOR组数量           : {len(xor_groups)}")
    print()

    # 2) data 丢失
    print("==== 帧的data包丢失（D < N） ====")
    if lost_data:
        print(f"数量: {len(lost_data)}")
        print("帧ID列表（按升序）:")
        print(", ".join(str(x) for x in sorted(lost_data)))
    else:
        print("无")
    print()

    # 3) 可由 FEC 恢复
    print("==== 可由FEC恢复的帧（D + F >= N） ====")
    if fec_ok:
        print(f"数量: {len(fec_ok)}")
        print("帧ID列表（按升序）:")
        print(", ".join(str(x) for x in sorted(fec_ok)))
    else:
        print("无")
    print()

    # 4) 可由 XOR 恢复
    print("==== 可由XOR恢复的帧（D + F == N - 1 且存在仅此一帧不完整的XOR组） ====")
    if xor_ok:
        print(f"数量: {len(xor_ok)}")
        print("帧ID列表（按升序）:")
        print(", ".join(str(x) for x in sorted(xor_ok)))
    else:
        print("无")
    print()

    # 5) N未知的帧（无法严格判断）
    if unknown_N:
        print("==== N未知（从未出现data行）帧（无法严格判断丢失/可恢复） ====")
        print(f"数量: {len(unknown_N)}")
        print("帧ID列表（按升序）:")
        print(", ".join(str(x) for x in sorted(unknown_N)))
        print("提示：若想对这些帧也做判定，需要在日志中看到至少一行该帧的data记录以获得 frame_pkt_num。")
        print()

if __name__ == "__main__":
    main()
